/***
 * @Description: 本文件是应用文件，使用操作系统自带的API进行ping操作的实现，主要定义ping函数的调用接口和数据结构
 * @Author: Liverpool
 * @Date: 2024-08-08 00:50:56
 * @LastEditTime: 2024-08-08 01:00:38
 * @LastEditors: Liverpool
 */

#include "ping.h"
#include <WinSock2.h> //引入windows内置的网络编程文件
#include "sys_plat.h" //引入系统平台文件
#include "net_api.h"

/***
 * @description: 对16位的数据做校验和相加处理(IP数据报的头部校验处理)
 * @Author: Liverpool
 * @Date: 2024-07-09 18:38:54
 * @return {*} 返回的最后的16位数据值
 * @param {void} *buf 需要校验的数据块的内存地址
 * @param {uint16_t} len 需要校验的数据块的数据长度
 */
uint16_t checksum(void *buf, uint16_t len)
{
    uint16_t *curr_buf = (uint16_t *)buf; // 进行16位数据的转化
    uint32_t checksum = 0;                // 保留上一块的计算值
    while (len > 1)
    {
        checksum += *curr_buf;
        curr_buf++;
        len -= 2;
    }
    // 对校验长度为奇数的数据块进行处理
    if (len > 0)
    {
        checksum += *((uint8_t *)curr_buf);
    }
    uint16_t high;
    // 把校验和的高16位加在低16位上，直至变为0
    while ((high = checksum >> 16) != 0)
    {
        checksum = high + (checksum & 0xFFFF);
    }
    // 根据是否取反来返回不同的值
    return (uint16_t)~checksum;
}

/***
 * @description: Ping函数的运行命令
 * @Author: Liverpool
 * @Date: 2024-08-08 01:02:07
 * @return {*}
 * @param {D_ping} *ping 定义的ping数据结构的内存指针位置
 * @param {char} *dest 需要进行ping操作的目标地址
 * @param {int} count ping的个数
 * @param {int} size ping的报文长度
 * @param {int} interval 两次ping之间的时间间隔
 */
void ping_run(D_ping *ping, const char *dest, int count, int size, int interval)
{
    WSADATA wsaData;                      // 需要进行相应的socket库绑定,使用Socket的程序在使用Socket之前必须调用WSAStartup函数
    WSAStartup(MAKEWORD(2, 2), &wsaData); // MAKEWORD是将两个byte型合并成一个word型，一个在高8位(b)，一个在低8位(a),使用WINSOCK2版本

    // 1.创建套接字
    int socket1 = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); // 使用IPv4协议 原始套接字的形式进行传输 IPPTOTO_ICMP使用ICMP协议
    if (socket1 == INVALID_SOCKET)                         // 获取的socket关键字不合法
    {
        plat_printf("ping:open socket error!\n");
        return;
    }
    // 设置socket接收时间过长，就错误
    // int tmo = 3000;
    struct timeval tmo;
    tmo.tv_sec = 3000;
    tmo.tv_usec = 0;
    setsockopt(socket1, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tmo, sizeof(tmo));
    // 2.对需要连接的IP地址进行指定
    struct sockaddr_in addr; // 服务器端的IP地址
    plat_memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;              // 使用IPv4协议
    addr.sin_addr.s_addr = inet_addr(dest); // 把字符串IP地址变为整形 并赋给服务器IP地址结构体
    addr.sin_port = 0;                      // ICMP协议不需要使用端口号，所以为0
#if PING_SOCKET_MODE
    connect(socket1, (struct sockaddr *)&addr, sizeof(addr));
#endif
    // 对发送的ping请求的buf进行赋值
    int fill_size = size > PING_BUFFER_SIZE ? PING_BUFFER_SIZE : size;
    for (int i = 0; i < fill_size; i++)
    {
        ping->request.echo_buf[i] = i;
    }
    static uint16_t start_id = PING_REQUEST_HEADER_ID;
    uint16_t seq = 0;
    // 根据需要发送的次数进行发送ping信息
    int total_len = fill_size + sizeof(D_icmp_hdr);
    for (int i = 0; i < count; i++, seq++)
    {
        // 封装ICMP头部信息
        ping->request.echo_hdr.type = PING_REQUEST_HEADER_TYPE;
        ping->request.echo_hdr.code = PING_REQUEST_HEADER_CODE;
        ping->request.echo_hdr.checksum = 0;
        ping->request.echo_hdr.id = htons(start_id);
        ping->request.echo_hdr.seq = seq;
        ping->request.echo_hdr.checksum = checksum(&ping->request, total_len);
// 进行数据包的发送
#if PING_SOCKET_MODE
        int size = send(socket1, (const char *)&ping->request, total_len, 0);
#else
        int size = sendto(socket1, (const char *)&ping->request, total_len, 0, (const struct sockaddr *)&addr, sizeof(addr));
#endif
        if (size < 0)
        {
            plat_printf("send ping request failed.\n");
            break;
        }
        clock_t time = clock();
        // 等待回应
        memset(&ping->reply, 0, sizeof(ping->reply));
        do
        {
#if PING_SOCKET_MODE
            size = recv(socket1, (char *)&ping->reply, sizeof(ping->reply), 0);
#else
            struct sockaddr_in from_addr; // ping回应的地址
            socklen_t addr_len = sizeof(from_addr);
            size = recvfrom(socket1, (char *)&ping->reply, sizeof(ping->reply), 0, (struct sockaddr *)&from_addr, &addr_len);
#endif
            if (size < 0)
            {
                plat_printf("send ping replay failed.\n");
                break;
            }
            if ((ping->request.echo_hdr.id == ping->reply.echo_hdr.id) && (ping->request.echo_hdr.seq == ping->reply.echo_hdr.seq))
            {
                break;
            }
        } while (1);
        // 进行收发数据比较
        if (size > 0)
        {
            int recv_size = size - sizeof(D_ip_hdr) - sizeof(D_icmp_hdr);
            if (memcmp(ping->request.echo_buf, ping->reply.echo_buf, recv_size))
            {
                printf("receive data error\n");
                continue;
            }
            D_ip_hdr *ip_hdr = &ping->reply.ip_hdr;
            int send_size = fill_size;
            if (send_size == recv_size)
            {
                plat_printf("replay from %s: bytes=%d, ", inet_ntoa(addr.sin_addr), recv_size);
            }
            else
            {
                plat_printf("replay from %s: bytes=%d (send=%d), ", inet_ntoa(addr.sin_addr), recv_size, send_size);
            }
            int diff_ms = (clock() - time) / (CLOCKS_PER_SEC / 1000); // 进行ms计算
            if (diff_ms < 1)
            {
                plat_printf("  time < 1ms, TTL=%d\n", ip_hdr->ttl);
            }
            else
            {
                plat_printf("  time = %dms, TTL=%d\n", diff_ms, ip_hdr->ttl);
            }
        }
        sys_sleep(interval);
    }
    closesocket(socket1);
}
